class CEChain:
    def __init__(self, methodToRun, oracle, validInput, failureInput, passLocations, failLocations):
        self.__isFinal = False
        # store the arguments
        self.__methodToTest = methodToRun
        self.__oracle = oracle
        self.__passInput = validInput
        self.__failInput = failureInput
        self.__passLocations = passLocations
        self.__failLocations = failLocations
        # init variables where we store the chains
        self.__ceChain_pass = {}
        self.__ceChain_fail = {}
        self.__ceChain_diff = {}
        self.__ceChain_cause = []
        
    def __len__(self):
        return len(self.__ceChain_cause)

    def addValueToChain(self, category, (line,iteration,cause)):
        if not self.__isFinal:
            if category == "PASS":
                self.__ceChain_pass[(line,iteration)] = cause
            elif category == "FAIL":
                self.__ceChain_fail[(line,iteration)] = cause
            elif category == "DIFF":
                self.__ceChain_diff[(line,iteration)] = cause
            elif category == "CAUSE":
                self.__ceChain_cause.append((line,iteration,cause))
            else:
                raise NameError("Undefined category in addValueToChain: ", repr(category))
    
    def finalize(self):
        self.__isFinal = True    

    def isFinal(self):
        return self.__isFinal
    
    def getNameOfMethodToTest(self):
        return self.__methodToTest.__name__    
        
    def getPassingInput(self):
        return self.__passInput
    
    def getFailingInput(self):
        return self.__failInput 
     
    def getPassingLocations(self):
        return self.__passLocations 
     
    def getFailingLocations(self):
        return self.__failLocations 
        
    def getCauseEffectChain(self):
        return self.__ceChain_cause
    
    def getFailingCoverage(self):
        coverage = set()
        for (line, _) in self.__failLocations:
            coverage.add(line)
        return coverage
    
    def getCauseEffectChainAsString(self):
        # init variables
        causedict = {}
        output = []
        # init output
        output.append("The program was started with " + str(self.__failInput))
        for ((_, _, cause)) in self.__ceChain_cause:
            for (var, value) in cause:
                # check if we haven't seen the diff before
                if var not in causedict.keys():
                    causedict[var] = value
                    last_changed_var = var
                    output.append("Then " + str(var) + " became " + str(value))
                # check if the diff changed and a different var has changed
                elif causedict[var] != value and last_changed_var != var:
                    causedict[var] = value
                    last_changed_var = var
                    output.append("Then " + str(var) + " became " + str(value))
        return output
    
    def printChain(self,depth=0):
        print self.toString(depth)
    
    def toString(self,depth=0):
        assert depth>=0
        indent = '\t'*depth
        s = indent+'The program was started with %r\n'%self.__failInput
        for (line,iteration,cause) in self.__ceChain_cause:
            itertext = ' iteration %d'%iteration if iteration>1 else ''
            for (var,value) in cause:                
                s += indent+'Then {} became {!r}\t(detected after line {}{})\n'.format(var,value,line,itertext)
        s+= indent+'Then the program failed.\n\n'
        return s
    
    def toStringParallel(self,depth=0):
        assert depth>=0
        indent = '\t'*depth
        s = indent+'The program was started with {!r}\t(in pass {!r})\n'.format(self.__failInput,self.__passInput)
        for (line,iteration,cause) in self.__ceChain_cause:
            itertext = ' iteration %d'%iteration if iteration>1 else ''
            for (var,value) in cause:
                passState = self.__ceChain_pass[(line,iteration)]
                if passState and passState.has_key(var):
                    value1 = '(in pass {!r})'.format(passState[var])
                else:
                    value1 = '(not in pass!)'
                s += indent+'Then {} became {!r}\t{}\t(detected after line {}{})\n'.format(var,value,value1,line,itertext)
        s+= indent+'Then the program failed.\n\n'
        return s
        
    
    def __str__(self):
        s = '{}: {!s} vs. {!s}\n'.format(self.__methodToTest.__name__,self.__passInput,self.__failInput)
        s+= 'Locations\t{}\n'.format([('%d+'%l if i>1 else l) for (l,i) in self.__locations])
        s+= 'PASS\t({})\t{}\n'.format(len(self.__ceChain_pass),self.__ceChain_pass)
        s+= 'FAIL\t({})\t{}\n'.format(len(self.__ceChain_fail),self.__ceChain_fail)
        s+= 'DIFF\t({})\t{}\n'.format(len(self.__ceChain_diff),self.__ceChain_diff)
        s+= 'CAUSE\t({})\t{}\n'.format(len(self.__ceChain_cause),self.__ceChain_cause)
        return s
